package fast.calc;

import fast.data.Spec;
import fast.data.Stat;
import fast.data.StatBlock;
import fast.data.WeaponType;
import fast.loader.Race;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class Stats extends StatBlock {
	
	// LEVEL_90
	public static double HASTE_CONVERSION_RATING = 425.;
	public static double HIT_CONVERSION_RATING = 340.;
	public static double EXP_CONVERSION_RATING = 340.;
	public static double MASTERY_CONVERSION_RATING = 600.;
	public static double CRIT_CONVERSION_RATING = 600.;
	public static double AGI_TO_CRIT_CONVERSION_RATING = 1259.518066406250000;
	public static double BOSS_ARMOR = 24835.;
	
	static double AGI_INTERCEPT = -0.003000000026077;
	
	static double BASE_STYLE_MISS_CHANCE = 7.5;
	static double BASE_SPELL_MISS_CHANCE = 15.0;
	static double BASE_DODGE_CHANCE = 7.5;
	static double DUAL_WIELD_PENALTY = 19.0;
	
	private Race race;
	private Spec spec;

	private WeaponStats mh;
	private WeaponStats oh;
	
	private Buffs buffs;
	private Procs procs = new Procs();
	private GearBuffs gearBuffs = new GearBuffs();
	private List<String> glyphs = new ArrayList<String>();
	private Set<String> talents = new HashSet<String>();
	
	public String calcEP = "";
	
	public double getBuffed(Stat stat){
		switch(stat){
			case agi:
				return getAgiBuffed();
			case str:
				return getStrBuffed();
			case ap:
				return getApBuffed();
			case cri:
				return getCritBuffed();
			case hst:
				return getHasteBuffed();
			case mst:
				return getMasteryBuffed();
			default:
				break;
		}
		return 0;
	}
	private double getStrBuffed(){
		double str = getStat(Stat.str);
		if(buffs.hasBuff("allstats_buff"))
			str *= 1.05;
		return str;
	}
	private double getAgiBuffed() {
		double agi = getStat(Stat.agi);
		agi += procs.getStat(Stat.agi);
		if(buffs.hasBuff("agi_flask"))
			agi += 1000; // LEVEL_90
		if(buffs.hasBuff("agi_food")){
			double food = 300; // LEVEL_90
			if(gearBuffs.has("epicurean"))
				food *= 2;
			agi += food;
		}
		if(buffs.hasBuff("agi_pot")){
			agi += 4000 * 25 / Fast.FIGHT_LENGTH;
		}
		if(buffs.hasBuff("agi_prepot")){
			agi += 4000 * 22 / Fast.FIGHT_LENGTH;
		}
		if(spec == Spec.Subtlety)
			agi *= 1.3;
		if(buffs.hasBuff("allstats_buff"))
			agi *= 1.05;
		agi *= 1.05; // leather spec baseline
		return agi;
	}
	private double getAgiBuffedStatic() {
		double agi = getStat(Stat.agi);
		if(buffs.hasBuff("agi_flask"))
			agi += 1000; // LEVEL_90
		if(buffs.hasBuff("agi_food")){
			double food = 300; // LEVEL_90
			if(gearBuffs.has("epicurean"))
				food *= 2;
			agi += food;
		}
		if(spec == Spec.Subtlety)
			agi *= 1.3;
		if(buffs.hasBuff("allstats_buff"))
			agi *= 1.05;
		agi *= 1.05; // leather spec baseline
		return agi;
	}
	private double getApBuffed() {
		double ap = getStat(Stat.ap) + getStrBuffed() + 2 * getAgiBuffed();
		ap += procs.getStat(Stat.ap);
		if(gearBuffs.has("blood_fury"))
			ap += 4514 * 15. / 120.;
		if(spec == Spec.Combat)
			ap *= 1.3;
		if(buffs.hasBuff("attackpower_buff"))
			ap *= 1.1;
		return ap;
	}
	private double getCritBuffed() {
		return getStat(Stat.cri)+procs.getStat(Stat.cri);
	}
	public double getCritPercent(){
		double RATING_AGI_TO_CRIT = AGI_TO_CRIT_CONVERSION_RATING;
		double crit_reduction = -3.0;
		double agi_crit_depression = AGI_INTERCEPT;
		double crit_buffs = 0.0;
		if(buffs.hasBuff("crit_buff"))
			crit_buffs += 5.0;
		if(gearBuffs.has("viciousness"))
			crit_buffs += 1.0;
		double agi_crit = this.getAgiBuffed()/RATING_AGI_TO_CRIT+agi_crit_depression;
		return (this.getBuffed(Stat.cri)/CRIT_CONVERSION_RATING + crit_buffs+crit_reduction+agi_crit)/100;
	}
	public double getCritPercentStatic(){
		double RATING_AGI_TO_CRIT = AGI_TO_CRIT_CONVERSION_RATING;
		double crit_reduction = -3.0;
		double agi_crit_depression = AGI_INTERCEPT;
		double crit_buffs = 0.0;
		if(buffs.hasBuff("crit_buff"))
			crit_buffs += 5.0;
		if(gearBuffs.has("viciousness"))
			crit_buffs += 1.0;
		double agi_crit = this.getAgiBuffedStatic()/RATING_AGI_TO_CRIT+agi_crit_depression;
		return (getStat(Stat.cri)/CRIT_CONVERSION_RATING + crit_buffs+crit_reduction+agi_crit)/100;
	}
	private double getHasteBuffed() {
		return getStat(Stat.hst)+procs.getStat(Stat.hst);
	}
	private double getMasteryBuffed() {
		double mastery = 8. + (getStat(Stat.mst) + procs.getStat(Stat.mst)) / MASTERY_CONVERSION_RATING;
		if(buffs.hasBuff("mastery_buff"))
			mastery += 5.;
		return mastery;
	}
	public void setMh(WeaponStats mh) {
		this.mh = mh;
	}
	public WeaponStats getMh() {
		return mh;
	}
	public void setOh(WeaponStats oh) {
		this.oh = oh;
	}
	public WeaponStats getOh() {
		return oh;
	}
	public void setBuffs(Buffs buffs) {
		this.buffs = buffs;
	}
	public Buffs getBuffs() {
		return buffs;
	}
	public void setSpec(Spec spec) {
		this.spec = spec;
	}
	public Spec getSpec() {
		return spec;
	}
	public void setRace(Race race) {
		this.race = race;
	}
	public Race getRace() {
		return race;
	}
	
	public double get2HandHitChance() {
		double base_miss = BASE_STYLE_MISS_CHANCE;
		double base_dodge = BASE_DODGE_CHANCE;
		double hit_per = getStat(Stat.hit) / HIT_CONVERSION_RATING;
		double dodge_per = getStat(Stat.exp) / EXP_CONVERSION_RATING;
		
		if(mh.getType() == WeaponType.sword && (race == Race.human || race == Race.gnome))
			dodge_per += 1;
		if(mh.getType() == WeaponType.mace && (race == Race.human || race == Race.dwarf))
			dodge_per += 1;
		if(mh.getType() == WeaponType.axe && (race == Race.orc))
			dodge_per += 1;
		
		double misses = Math.max(base_miss - hit_per, 0);
		double dodges = Math.max(base_dodge - dodge_per, 0);
		
		if(calcEP.equals("exp_mh"))
			dodges += 1 / EXP_CONVERSION_RATING;
		if(calcEP.equals("style_hit") || calcEP.equals("white_hit"))
			misses += 1 / HIT_CONVERSION_RATING;
		
		return (100-misses-dodges)/100;
	}
	
	private double getRacialExpertiseBonus(WeaponStats w){
		double dodge_per = 0.0;
		if(w.getType() == WeaponType.dagger && (race == Race.gnome))
			dodge_per += 1;
		if(w.getType() == WeaponType.sword && (race == Race.human || race == Race.gnome))
			dodge_per += 1;
		if(w.getType() == WeaponType.mace && (race == Race.human || race == Race.dwarf))
			dodge_per += 1;
		if(w.getType() == WeaponType.axe && (race == Race.orc))
			dodge_per += 1;
		if(w.getType() == WeaponType.fist && (race == Race.orc))
			dodge_per += 1;
		return dodge_per;
	}
	
	public double getMhHitChance() {
		double base_miss = BASE_STYLE_MISS_CHANCE + DUAL_WIELD_PENALTY;
		double base_dodge = BASE_DODGE_CHANCE;
		double hit_per = getStat(Stat.hit) / HIT_CONVERSION_RATING;
		double dodge_per = getStat(Stat.exp) / EXP_CONVERSION_RATING;
		
		dodge_per += getRacialExpertiseBonus(mh);
				
		double misses = Math.max(base_miss - hit_per, 0);
		double dodges = Math.max(base_dodge - dodge_per, 0);
		
		if(calcEP.equals("exp_mh"))
			dodges += 1 / EXP_CONVERSION_RATING;
		if(calcEP.equals("style_hit") || calcEP.equals("white_hit"))
			misses += 1 / HIT_CONVERSION_RATING;
		
		return (100-misses-dodges)/100;
	}
	
	public double getOhHitChance() {
		double base_miss = BASE_STYLE_MISS_CHANCE + DUAL_WIELD_PENALTY;
		double base_dodge = BASE_DODGE_CHANCE;
		double hit_per = getStat(Stat.hit)/ HIT_CONVERSION_RATING;
		double dodge_per = getStat(Stat.exp) / EXP_CONVERSION_RATING;
		
		dodge_per += getRacialExpertiseBonus(oh);
		
		double misses = Math.max(base_miss - hit_per, 0);
		double dodges = Math.max(base_dodge - dodge_per, 0);
		
		if(calcEP.equals("exp_oh"))
			dodges += 1 / EXP_CONVERSION_RATING;
		if(calcEP.equals("style_hit") || calcEP.equals("white_hit"))
			misses += 1 / HIT_CONVERSION_RATING;
		
		return (100-misses-dodges)/100;
	}
	
	public double getMhStyleHitChance() {
		double base_miss = BASE_STYLE_MISS_CHANCE;
		double base_dodge = BASE_DODGE_CHANCE;
		double hit_per = getStat(Stat.hit) / HIT_CONVERSION_RATING;
		double dodge_per = getStat(Stat.exp) / EXP_CONVERSION_RATING;
		
		dodge_per += getRacialExpertiseBonus(mh);
		
		double misses = Math.max(base_miss - hit_per, 0);
		double dodges = Math.max(base_dodge - dodge_per, 0);
		
		if(calcEP.equals("exp_mh"))
			dodges += 1 / EXP_CONVERSION_RATING;
		if(calcEP.equals("style_hit"))
			misses += 1 / HIT_CONVERSION_RATING;
		
		return (100-misses-dodges)/100;
	}
	
	public double getOhStyleHitChance() {
		double base_miss = BASE_STYLE_MISS_CHANCE;
		double base_dodge = BASE_DODGE_CHANCE;
		double hit_per = getStat(Stat.hit) / HIT_CONVERSION_RATING;
		double dodge_per = getStat(Stat.exp)/ EXP_CONVERSION_RATING;
		
		dodge_per += getRacialExpertiseBonus(oh);
		
		double misses = Math.max(base_miss - hit_per, 0);
		double dodges = Math.max(base_dodge - dodge_per, 0);
		
		if(calcEP.equals("exp_oh"))
			dodges += 1 / EXP_CONVERSION_RATING;
		if(calcEP.equals("style_hit"))
			misses += 1 / HIT_CONVERSION_RATING;
		
		return (100-misses-dodges)/100;
	}
	
	public double getMhPoisonHitChance() {
		double base_miss = BASE_STYLE_MISS_CHANCE;
		double hit_per = getStat(Stat.hit) / HIT_CONVERSION_RATING;
		
		double misses = Math.max(base_miss - hit_per, 0);
		
		if(calcEP.equals("style_hit"))
			misses += 1 / HIT_CONVERSION_RATING;
		
		return (100-misses)/100;
	}
	
	public double getOhPoisonHitChance() {
		double base_miss = BASE_STYLE_MISS_CHANCE;
		double hit_per = getStat(Stat.hit) / HIT_CONVERSION_RATING;
			
		double misses = Math.max(base_miss - hit_per, 0);
		
		if(calcEP.equals("style_hit"))
			misses += 1 / HIT_CONVERSION_RATING;
		
		return (100-misses)/100;
	}
	
	public double getSpellHitChance() {
		double base_miss = BASE_SPELL_MISS_CHANCE;
		double hit_per = getStat(Stat.hit) / HIT_CONVERSION_RATING;
		double dodge_per = getStat(Stat.exp) / EXP_CONVERSION_RATING;
		
		dodge_per += getRacialExpertiseBonus(mh);
		
		double misses = Math.max(base_miss - hit_per - dodge_per, 0);
		
		if(calcEP.equals("exp_mh"))
			misses += 1 / EXP_CONVERSION_RATING;
		if(calcEP.equals("style_hit"))
			misses += 1 / HIT_CONVERSION_RATING;
		
		return (100-misses)/100;
	}
	
	public double getCritMultiplier() {
		double base = 2.0;
		if(gearBuffs.has("metagem_critdmg"))
			base *= 1.03;
		return base;
	}
	public GearBuffs getGearBuffs() {
		return gearBuffs;
	}
	public void setGearBuffs(List<String> gbs) {
		gearBuffs.setGearBuffs(gbs);
	}
	public void setSetItems(Set<String> set) {
		gearBuffs.setSetItems(set);	
	}
	public List<String> getGlyphs() {
		return glyphs;
	}
	public void setGlyphs(List<String> glyphs) {
		this.glyphs = glyphs;
	}
	public boolean hasGlyph(String name) {
		return this.glyphs.contains(name);
	}
	public void setTalents(Set<String> talents) {
		this.talents = talents;
	}
	public boolean hasTalent(String name) {
		return this.talents.contains(name);
	}
	public void setGearBuffItemLevel(HashMap<String, Integer> gearBuffItemLevel) {
		gearBuffs.setGearBuffItemLevel(gearBuffItemLevel);	
	}
	public Procs getProcs() {
		return procs;
	}
}
